// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: lucasyoo

#include "coin2/strategy/order_executor/layering_executor3.h"

namespace coin2::strategy::order_executor {

LayeringExecutor3::LayeringExecutor3(
    const PassiveExecutorConfig& config,
    IOrderSubsystem* os,
    std::optional<int64_t> timestamp,
    const TopBook* book,
    GetProductHolderFunc get_product_holder,
    std::optional<double> qty_tolerance_bp,
    std::optional<double> lot_size_quote,
    std::optional<double> avoid_qty_cancel_percent,
    const bool is_randomize_post,
    const int cancel2order_ratio,
    const bool verbose)
    : LayeringExecutor2(config, os, timestamp, book, get_product_holder, lot_size_quote, verbose),
      qty_tolerance_bp_(qty_tolerance_bp),
      avoid_qty_cancel_percent_(avoid_qty_cancel_percent),
      is_randomize_post_(is_randomize_post),
      cancel2order_ratio_(cancel2order_ratio) {}


std::vector<LayeringLevels::Level> LayeringExecutor3::GetMostDesiredModification(
    LayeringLevels layering_levels,
    int64_t timestamp,
    int sign,
    const OrderList& wo_list,
    LayeringExecutor3* layering_executor) {
  double level_bp = 2;
  const auto& levels = layering_levels.levels;
  LogAsIsToBe(timestamp, sign, wo_list, levels);

  auto woit = wo_list.cbegin();
  auto lvit = levels.cbegin();

  std::vector<LayeringLevels::Level> cancel_levels{};
  std::vector<LayeringLevels::Level> post_levels{};
  bool is_post_mode =
      cancel2order_ratio_ <= current_num_cancel_;  // true <- Only Post if there's POST else cancel
                                                    // one order and reset current_num_cancel_
  OrderList max_outside_orders;
  if (verbose_) {
    LOG(INFO) << "is_post_mode: " << is_post_mode << " current_num_cancel_: " << current_num_cancel_
              << " cancel2order_ratio_: " << cancel2order_ratio_;
  }
  auto check_loop = [this, &woit, &lvit, &wo_list, &levels, &is_post_mode]() {
    if (is_post_mode && is_randomize_post_)
      return lvit != levels.cend();
    else
      return woit != wo_list.cend() || lvit != levels.cend();
  };
  for (; check_loop();) {
    auto comp = LayeringLevels::TIE;
    if (woit != wo_list.cend() && lvit != levels.cend()) {
      double price_tolerance_bp = 0;
      if (woit == wo_list.cbegin() || lvit == levels.cbegin()) {
        price_tolerance_bp = 2;
      } else {
        price_tolerance_bp = level_bp;
      }
      if (qty_tolerance_bp_.has_value()) {
        comp = LayeringLevels::ComparePriceQty(
            {(*woit)->order_price(), (*woit)->working_qty()},
            {lvit->price, lvit->qty},
            {price_tolerance_bp, qty_tolerance_bp_.value()},
            avoid_qty_cancel_percent_,
            sign);
        if (verbose_)
          SPDLOG_INFO(
              "{} @{} vs {} @{} : {}",
              (*woit)->working_qty(),
              (*woit)->order_price(),
              lvit->qty,
              lvit->price,
              comp);
      } else {
        comp = LayeringLevels::ComparePrice(
            (*woit)->order_price(),
            lvit->price,
            price_tolerance_bp,
            sign);
        if (verbose_) SPDLOG_INFO("{} vs {} : {}", (*woit)->order_price(), lvit->price, comp);
      }
    }
    if (woit == wo_list.cend() || comp == LayeringLevels::OUTSIDE) {  // OUTSIDE: POST
      if (!cancel_levels.empty() && !is_post_mode) return cancel_levels;
      CHECK_EQ(lvit->opertype, LayeringLevels::POST);
      if (!max_outside_orders.empty() &&
          lvit->price * sign > max_outside_orders.back()->order_price() * sign) {
        if (is_post_mode && !post_levels.empty()) {
          break;  // POST mode and already has post_levels -> break
        } else if (is_post_mode && post_levels.empty()) {
          if (verbose_) SPDLOG_INFO("POST mode and no post_levels -> RETURN CANCEL_MAX_OUTSIDE");
          return {LayeringLevels::Level::ConstructCancelLevel(max_outside_orders.back())};
        } else {
          cancel_levels.push_back(
              LayeringLevels::Level::ConstructCancelLevel(max_outside_orders.back()));
          if (verbose_) SPDLOG_INFO("RETURN CANCEL_MAX_OUTSIDE");
          return cancel_levels;
        }
      } else if (wo_list.size() >= levels.size()) {  // Full but it tries to POST -> Cancel
                                                     // iterating from Most Outside Order
        bool diff_found = false;
        auto woit_back = wo_list.crbegin();
        auto lvit_back = levels.crbegin();
        for (; !diff_found;) {
          if ((*woit_back)->order_price() * sign < lvit_back->price * sign) {
            cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(*woit_back));
            diff_found = true;
          } else if (woit_back == wo_list.crend() || lvit_back == levels.crend()) {
            cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(wo_list.back()));
            diff_found = true;
          }
          ++woit_back;
          ++lvit_back;
        }
        return cancel_levels;
      } else {
        if (is_randomize_post_) {
          if (!is_post_mode)  // Want to POST but randomize -> make it post_mode
            is_post_mode = true;
          if (verbose_) SPDLOG_INFO("MAKE POST MODE AND ADD POST LEVELS");
          post_levels.push_back(*lvit);  // collect POST for randomize
          ++lvit;
        } else {
          if (verbose_) SPDLOG_INFO("MAKE POST MODE AND RETURN POST");
          return std::vector<LayeringLevels::Level>{*lvit};
        }
      }
    } else if (lvit == levels.cend() || comp == LayeringLevels::INSIDE) {  // INSIDE: CANCEL
      if (cancel_levels.size() + current_num_cancel_ < cancel2order_ratio_) {
        if (verbose_) SPDLOG_INFO("UNDER CANCEL2ORDER_RATIO -> ADD CANCEL");
        cancel_levels.push_back(LayeringLevels::Level::ConstructCancelLevel(*woit));
      }
      if (levels.empty() ||
          (!levels.empty() &&
           levels.cbegin()->price * sign < (*woit)->order_price() * sign)) {  // emergency cancels
        if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
        ++woit;
      } else if (!is_post_mode) {
        if (verbose_) SPDLOG_INFO("NOT_POST_MODE -> RETURN CANCEL");
        return cancel_levels;
      } else {
        ++woit;
      }
    } else if (comp == LayeringLevels::TIE) {
      if (!cancel_levels.empty() && !is_post_mode) {
        if (verbose_) SPDLOG_INFO("TIE BUT NO POST_MODE -> RETURN CANCEL");
        return cancel_levels;
      }
      if (woit != wo_list.cend()) max_outside_orders.push_back(*woit);
      ++lvit;
      ++woit;
    }
    // Cancel Full -> return cancel_levels
    if (!is_post_mode && cancel_levels.size() + current_num_cancel_ >= cancel2order_ratio_)
      return cancel_levels;
  }
  if (is_post_mode && !post_levels.empty()) {
    return {post_levels[randomize_post_dist_(gen_) % post_levels.size()]};
  } else {
    return cancel_levels;
  }
}

void LayeringExecutor3::ManageSideOrders(int64_t timestamp, int sign, LayeringLevels levels) {
  if (!wo_) {
    SPDLOG_INFO_EVERY_NS(60e9, timestamp, "wo_ not ready");
    return;
  }
  if (!is_wo_ordered_) UpdateOrders(timestamp);
  levels.ClipQty(GetMaximumWorkingQty(sign), GetLotSizeQuote(), verbose_);

  CHECK_EQ(levels.sign, sign);

  const auto& order_list = sign > 0 ? ordered_buy_list_ : ordered_sell_list_;
  auto opers = this->GetMostDesiredModification(levels, timestamp, sign, order_list, this);
  for (const auto& oper : opers) {
    auto operkey = std::pair<int, LayeringLevels::OperationType>(sign, oper.opertype);
    if (oper.opertype == LayeringLevels::POST &&
        prev_oper_ts_[operkey] + order_update_period_ < timestamp) {
      SPDLOG_INFO("POST) sgn:{} {} @{}", sign, oper.qty, oper.price);
      this->ExecuteOperation(timestamp, sign, oper);
      prev_oper_ts_[operkey] = timestamp;
    } else if (oper.opertype == LayeringLevels::CANCEL) {
      this->ExecuteOperation(timestamp, sign, oper);
    }
  }
}

void LayeringExecutor3::ExecuteOperation(
    int64_t timestamp,
    int sign,
    const LayeringLevels::Level& oper) {
  OrderSide side;
  switch (sign) {
    case 1:
      side = OrderSide::BUY_ORDER;
      break;
    case -1:
      side = OrderSide::SELL_ORDER;
      break;
    default:
      NOTREACHED() << sign;
  }
  switch (oper.opertype) {
    case LayeringLevels::CANCEL: {
      if (!os_->gateway()->need_exchange_id_to_cancel() &&
          !cancel_cooldown_limiter_->InCoolDown(timestamp, *oper.proc_order_id)) {
        os_->gateway()->CancelOrder(*oper.proc_order_id);
        cancel_cooldown_limiter_->Update(timestamp, *oper.proc_order_id);
        SPDLOG_INFO("CANCEL) sgn:{} {} @{} order_id: {} exchange_order_id: {}", sign, oper.qty,
                    oper.price, *oper.proc_order_id,
                    oper.exchange_order_id.has_value() ? oper.exchange_order_id.value() : "N/A");
      } else if (
          os_->gateway()->need_exchange_id_to_cancel() &&
          !cancel_cooldown_limiter_->InCoolDown(timestamp, *oper.proc_order_id)) {
        const auto* oc = os_->processor().order_manager()->FindByProcOrderId(*oper.proc_order_id);
        if (oc->has_exchange_order_id()) {
          os_->gateway()->CancelOrder(*oper.proc_order_id);
          cancel_cooldown_limiter_->Update(timestamp, *oper.proc_order_id);
          SPDLOG_INFO("CANCEL) sgn:{} {} @{} order_id: {} exchange_order_id: {}", sign, oper.qty,
                      oper.price, *oper.proc_order_id,
                      oper.exchange_order_id.has_value() ? oper.exchange_order_id.value() : "N/A");
        } else {
          // pass, cannot cancel if exchange order id is not ready.
        }
      }
    } break;
    case LayeringLevels::POST: {
      const auto& order_price = oper.price;
      const auto& order_qty = oper.qty;
      const std::string& tag =
          oper.filter_tag.has_value() ? oper.filter_tag.value() : this->config().filter_tag();
      constexpr bool post_only = true;
      SPDLOG_INFO("{}: {}/{}", (side == OrderSide::BUY_ORDER ? "BUY_ORDER" : "SELL_ORDER"),
                  order_price, order_qty);
      OrderSpec order_spec(
          this->product(),
          OrderType::LIMIT_ORDER,
          side,
          OrderDuration::GTC_ORDER,
          order_price,
          order_qty,
          timestamp,
          post_only,
          this->config().leverage(),
          tag);
      if (config_.reduce_only()) {
        order_spec.SetReduceOnly(true);
      }
      if (this->GetEnableOrderSubmission()) {
        os_->gateway()->SubmitOrder(order_spec);
        current_num_cancel_ = 0;
      }

      if (side == OrderSide::BUY_ORDER) {
        this->SetBuyTimestamp(timestamp);
      } else if (side == OrderSide::SELL_ORDER) {
        this->SetSellTimestamp(timestamp);
      } else {
        NOTREACHED();
      }
    } break;
    default:
      NOTREACHED() << oper.opertype;
  }
}

}  // namespace coin2::strategy::order_executor
